package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name  string `json:"name"`
	Age   int    `json:"age"`
	Score int    `json:"score"`
}

func (s Student) GetInfo() string {
	var str = fmt.Sprintf("姓名: %v, 年龄: %v, 成绩: %v", s.Name, s.Age, s.Score)
	return str
}

func (s *Student) SetInfo(name string, age int, score int) {
	s.Name = name
	s.Age = age
	s.Score = score
}

func (s Student) Print() {
	fmt.Println("这是一个打印方法")
}

// 打印字段
func PrintStructField(s interface{}) {
	t := reflect.TypeOf(s)
	// 如果传入的是指针类型，获取其指向的实际结构体类型
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	// 再次确认是否为结构体类型，只有是结构体类型才能进行后续字段相关操作
	if t.Kind() != reflect.Struct {
		fmt.Println("不是结构体类型")
		return
	}

	// 1. 通过类型变量里面的Field获取结构体的字段
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		fmt.Printf("字段名: %v, 字段类型: %v\n", field.Name, field.Type)
		fmt.Printf("字段标签: %v\n", field.Tag.Get("json")) // json对应的是结构体字段的标签
	}
	fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")

	// 2. 通过类型变量里面的FieldByName可以获取结构体的字段（示例用法，比如按名称查找特定字段）
	nameField, ok := t.FieldByName("Name")
	if ok {
		fmt.Printf("通过名称获取字段：字段名: %v, 字段类型: %v 字段标签: %v\n", nameField.Name, nameField.Type, nameField.Tag.Get("json"))
	}
	fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")

	// 3. 通过类型变量里面的NumField可以获取结构体的字段数量（前面已经在循环中体现了这个用法）
	var fieldCount = t.NumField()
	fmt.Printf("结构体字段数量: %v\n", fieldCount)

	// 4. 获取结构体属性值（示例用法，比如获取结构体的Name属性值）
	v := reflect.ValueOf(s)
	fmt.Printf("结构体Name属性值: %v\n", v.FieldByName("Name"))

	fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
	for i := 0; i < fieldCount; i++ { //                                          v.Field(i)
		fmt.Printf("属性名称:%v 属性值:%v 属性类型:%v 属性tag:%v\n", t.Field(i).Name, v.Field(i).Interface(), v.Field(i).Type(), t.Field(i).Tag.Get("json"))
	}
}

// 打印执行方法
func PrintStructFn(s interface{}) {
	t := reflect.TypeOf(s)
	v := reflect.ValueOf(s)
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
		// v = v.Elem()
	}
	if t.Kind() != reflect.Struct {
		fmt.Println("不是结构体类型")
		return
	}

	//1. 通过类型变量里面的Method可以获取结构体方法
	Method0 := t.Method(0) // 0和结构体方法的顺序没有关系 和 结构体对应的ASCII码有关系
	fmt.Printf("方法名称: %v, 方法类型: %v\n", Method0.Name, Method0.Type)

	//2. 通过类型变量获取这个结构体有多少方法
	method1, ok := t.MethodByName("Print")
	if ok {
		fmt.Printf("通过名称获取方法：方法名称: %v, 方法类型: %v\n", method1.Name, method1.Type)
	}

	//3. 通过 值变量 执行方法
	v.Method(1).Call(nil)
	info := v.MethodByName("GetInfo").Call(nil)
	fmt.Println(info)

	//4. 执行方法传入参数 (注意需要只用 值变量， 并且要注意参数，接收的参数是[]reflect.Value的切片)
	var params []reflect.Value
	params = append(params, reflect.ValueOf("李四"))
	params = append(params, reflect.ValueOf(18))
	params = append(params, reflect.ValueOf(80))
	v.MethodByName("SetInfo").Call(params) // 执行方法传入参数

	info2 := v.MethodByName("GetInfo").Call(nil)
	fmt.Println(info2)

	//5. 获取方法数量
	methodCount := t.NumMethod()
	fmt.Printf("结构体方法数量: %v\n", methodCount)

	// 修改结构体属性值
	name := v.Elem().FieldByName("Name")
	name.SetString("四")

}

func main() {
	stu1 := Student{Name: "张三", Age: 20, Score: 90}
	// PrintStructField(&stu1)  //需要修改的话传入指针类型的数据
	// PrintStructField(stu1) //不需要修改值的话传入值类型的数据
	PrintStructFn(&stu1)
	fmt.Println(stu1.GetInfo())
}
